<?php
/**
 * @version		$Id: dispatcher.php 10381 2008-06-01 03:35:53Z pasamio $
 * @package		Joomla.Framework
 * @subpackage	Event
 * @copyright	Copyright (C) 2005 - 2008 Open Source Matters. All rights reserved.
 * @license		GNU/GPL, see LICENSE.php
 * Joomla! is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 * See COPYRIGHT.php for copyright notices and details.
 */

/**
 * Abstract observable class to implement the observer design pattern
 *
 * @abstract
 * @author		Louis Landry <louis.landry@joomla.org>
 * @package		Joomla.Framework
 * @subpackage	Base
 * @since		1.2.0
 */
class vmObservable {
	/**
	 * An array of Observer objects to notify
	 *
	 * @access private
	 * @var array
	 */
	var $_observers = array();

	/**
	 * The state of the observable object
	 *
	 * @access private
	 * @var mixed
	 */
	var $_state = null;


	/**
	 * Constructor
	 */
	function __construct() {
		$this->_observers = array();
	}

	/**
	 * Get the state of the JObservable object
	 *
	 * @access public
	 * @return mixed The state of the object
	 * @since 1.5
	 */
	function getState() {
		return $this->_state;
	}

	/**
	 * Update each attached observer object and return an array of their return values
	 *
	 * @access public
	 * @return array Array of return values from the observers
	 * @since 1.5
	 */
	function notify()
	{
		// Iterate through the _observers array
		foreach ($this->_observers as $observer) {
			$return[] = $observer->update();
		}
		return $return;
	}

	/**
	 * Attach an observer object
	 *
	 * @access public
	 * @param object $observer An observer object to attach
	 * @return void
	 * @since 1.5
	 */
	function attach( &$observer)
	{
		// Make sure we haven't already attached this object as an observer
		if (is_object($observer))
		{
			$class = get_class($observer);
			foreach ($this->_observers as $check) {
				if (is_a($check, $class)) {
					return;
				}
			}
			$this->_observers[] =& $observer;
		} else {
			$this->_observers[] =& $observer;
		}
	}

	/**
	 * Detach an observer object
	 *
	 * @access public
	 * @param object $observer An observer object to detach
	 * @return boolean True if the observer object was detached
	 * @since 1.5
	 */
	function detach( $observer)
	{
		// Initialize variables
		$retval = false;

		$key = array_search($observer, $this->_observers);

		if ( $key !== false )
		{
			unset($this->_observers[$key]);
			$retval = true;
		}
		return $retval;
	}
}

/**
 * Class to handle dispatching of events.
 *
 * This is the Observable part of the Observer design pattern
 * for the event architecture.
 *
 * @package 	Joomla.Framework
 * @subpackage	Event
 * @since 1.2.0
 * @see		JPlugin
 * @link http://dev.joomla.org/component/option,com_jd-wiki/Itemid,31/id,tutorials:plugins/ Plugins tutorial
 */
class vmDispatcher extends vmObservable {
	/**
	 * Constructor
	 *
	 * @access	protected
	 */
	function __construct() {
		parent::__construct() ;
	}
	
	/**
	 * Returns a reference to the global Event Dispatcher object, only creating it
	 * if it doesn't already exist.
	 *
	 * This method must be invoked as:
	 * 		<pre>  $dispatcher = &JDispatcher::getInstance();</pre>
	 *
	 * @access	public
	 * @return	JDispatcher	The EventDispatcher object.
	 * @since 1.2.0
	 */
	function &getInstance() {
		static $instance ;
		
		if( ! is_object( $instance ) ) {
			$instance = new vmDispatcher( ) ;
		}
		
		return $instance ;
	}
	
	/**
	 * Registers an event handler to the event dispatcher
	 *
	 * @access	public
	 * @param	string	$event		Name of the event to register handler for
	 * @param	string	$handler	Name of the event handler
	 * @return	void
	 * @since 1.2.0
	 */
	function register( $event, $handler ) {
		// Are we dealing with a class or function type handler?
		if( function_exists( $handler ) ) {
			// Ok, function type event handler... lets attach it.
			$method = array( 'event' => $event , 'handler' => $handler ) ;
			$this->attach( $method ) ;
		} elseif( class_exists( $handler ) ) {
			//Ok, class type event handler... lets instantiate and attach it.
			$this->attach( new $handler( $this ) ) ;
		} else {
			$GLOBALS['vmLogger']->err( 'vmDispatcher::register: Event handler not recognized - Handler: ' . $handler ) ;
		}
	}
	
	/**
	 * Triggers an event by dispatching arguments to all observers that handle
	 * the event and returning their return values.
	 *
	 * @access	public
	 * @param	string	$event			The event name
	 * @param	array	$args			An array of arguments
	 * @param	boolean	$doUnpublished	[DEPRECATED]
	 * @return	array	An array of results from each function call
	 * @since 1.2.0
	 */
	function trigger( $event, $args = null, $doUnpublished = false ) {
		// Initialize variables
		$result = array() ;
		
		/*
		 * If no arguments were passed, we still need to pass an empty array to
		 * the call_user_func_array function.
		 */
		if( $args === null ) {
			$args = array() ;
		}
		
		/*
		 * We need to iterate through all of the registered observers and
		 * trigger the event for each observer that handles the event.
		 */
		foreach( $this->_observers as $observer ) {
			if( is_array( $observer ) ) {
				/*
				 * Since we have gotten here, we know a little something about
				 * the observer.  It is a function type observer... lets see if
				 * it handles our event.
				 */
				if( $observer['event'] == $event ) {
					if( function_exists( $observer['handler'] ) ) {
						$result[] = call_user_func_array( $observer['handler'], $args ) ;
					} else {
						/*
						 * Couldn't find the function that the observer specified..
						 * wierd, lets throw an error.
						 */
						$GLOBALS['vmLogger']->err( 'vmDispatcher::trigger: Event Handler Method does not exist - Method called: ' . $observer['handler'] ) ;
					}
				} else {
					// Handler doesn't handle this event, move on to next observer.
					continue ;
				}
			} elseif( is_object( $observer ) ) {
				/*
				 * Since we have gotten here, we know a little something about
				 * the observer.  It is a class type observer... lets see if it
				 * is an object which has an update method.
				 */
				if( method_exists( $observer, 'update' ) ) {
					/*
					 * Ok, now we know that the observer is both not an array
					 * and IS an object.  Lets trigger its update method if it
					 * handles the event and return any results.
					 */
					if( method_exists( $observer, $event ) ) {
						$args['event'] = $event ;
						$result[] = $observer->update( $args ) ;
					} else {
						/*
						 * Handler doesn't handle this event, move on to next
						 * observer.
						 */
						continue ;
					}
				} else {
					/*
					 * At this point, we know that the registered observer is
					 * neither a function type observer nor an object type
					 * observer.  PROBLEM, lets throw an error.
					 */
					$GLOBALS['vmLogger']->err( 'vmDispatcher::trigger: Unknown Event Handler.' ) ;
				}
			}
		}
		return $result ;
	}
}
?>